#include <iostream>
#include <fstream>

#include "myfilelog.hxx"
#include "loghelper.hxx"
#include "wfaudiorecorder.hxx"

using namespace std;

WFAudioRecorder::WFAudioRecorder()
:	AudioRecorder(),_blocks(NULL),_device(NULL),_shutdown_requested(FALSE),_callback_thread_id(0)
{
	// all members got default value set defined in header file.
}

WFAudioRecorder::WFAudioRecorder(tUInt32 bits_sample,tUInt32 sample_sec,tUInt32 block_nb,tUInt32 block_size,
								 tUInt32 channel_nb,tUInt32 callback_thread_id,FileLog* filelog)
:	AudioRecorder(bits_sample,sample_sec,block_nb,block_size,channel_nb,filelog),
	_blocks(NULL),_device(NULL),_shutdown_requested(FALSE),_callback_thread_id(callback_thread_id)
{}

WFAudioRecorder::~WFAudioRecorder()
{
	UnInit();
}

tInt32 WFAudioRecorder::Init()
{
	if (!AllocateBlocks(_block_nb,_block_size))
	{
		LOGERROR("WFAudioRecorder::Init:	AllocateBlocks("<<_block_nb<<","<<_block_size<<") failed.");
		return -1;
	}

	/* Get the first available device index */
	tUInt32 count = waveInGetNumDevs();
	tUInt32 device_index = 0;
	WAVEINCAPS caps;
	for (tUInt32 i = 0; i < count; i++)
	{
		if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR)
		{
			device_index++;

			break;
		}
	}
	if (device_index == 0)
	{
		LOGERROR("WFAudioRecorder::Init:	No device available.");

		return -2;
	}

	/* Open the device */
	WAVEFORMATEX format;;
	format.cbSize = 0;
	format.wFormatTag = WAVE_FORMAT_PCM;
	format.nChannels = _channel_nb;;
	format.wBitsPerSample = _bits_per_sample;
	format.nSamplesPerSec = _samples_per_sec;
	format.nBlockAlign = format.nChannels * (format.wBitsPerSample >> 3);
	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;

	DWORD dwFlag = WAVE_FORMAT_DIRECT;
	if (_callback_thread_id) dwFlag |= CALLBACK_THREAD;

	MMRESULT err = waveInOpen(&_device, WAVE_MAPPER, &format, _callback_thread_id, (DWORD_PTR)&format, dwFlag);
	if (err != MMSYSERR_NOERROR)
	{
		LOGERROR("WFAudioRecorder::Init:	waveInOpen failed with err = "<<err<<".");

		return -3;
	}

	err = waveInStart(_device);
	if (err != MMSYSERR_NOERROR)
	{
		LOGERROR("WFAudioRecorder::Init:	waveInStart failed with err = "<<err<<".");

		return -4;
	}

	return 1;
}

void WFAudioRecorder::Execute(Thread::Arg arg)
{
	int header_counter = 0;
	tInt32 ret = 0;

	WAVEHDR* current;

	LOGINFO("WFAudioRecorder starting up...");

	while (!_shutdown_requested)
	{
		if (DecrFreeBlockCount() < 0)
		{
			Yield();

			//LOGDEBUG("WFAudioRecorder::Execute:	threadid #"<<Id()<<" no free block, Yield ...");

			continue;
		}

		// Get current block
		current = &_blocks[_current_block];

		// Unprepare current block
		if ((current->dwFlags & WHDR_DONE) == WHDR_DONE)
		{
			if(MMSYSERR_NOERROR == waveInUnprepareHeader(_device, current, sizeof(WAVEHDR)))
			{
				LOGDEBUG("WFAudioRecorder threadid #"<<Id()<<" waveInUnprepareHeader ...");
			}
			else
			{
				LOGERROR("WFAudioRecorder threadid #"<<Id()<<" waveInUnprepareHeader failed at  _current_block:"<<_current_block<<" _free_block_count:"<<_free_block_count);
			}
		}

		// Prepare current block and add it in for recording.
		if (waveInPrepareHeader(_device, current, sizeof(WAVEHDR)) == MMSYSERR_NOERROR &&
			MMSYSERR_NOERROR == waveInAddBuffer(_device, current, sizeof(WAVEHDR)))
		{
			LOGDEBUG("WFAudioRecorder threadid #"<<Id()<<" waveInAddBuffer  _current_block:"<<_current_block<<" _free_block_count:"<<_free_block_count<<",header_counter:"<<header_counter);
			header_counter++;
		}
		else
		{
			LOGERROR("WFAudioRecorder threadid #"<<Id()<<" waveInAddBuffer failed at  _current_block:"<<_current_block<<" _free_block_count:"<<_free_block_count);
		}

		// Move forward current block.
		_current_block++;
		_current_block %= _block_nb;
	}

	/**
	 * Wait for the record handler to finish processing the voice data in blocks
	 * But here still is a risk of blocks access vialation. Seek for a better way later.
	 */
	//while (IncrFreeBlockCount() < _block_nb)
	//{
	//	SleepMs(100);
	//}

	LOGINFO("WFAudioRecorder shutdowned.");

}
tInt32 WFAudioRecorder::UnInit()
{
	tInt32 ret = 0;

	MMRESULT mmr;

	mmr = waveInReset(_device);

	if (mmr != MMSYSERR_NOERROR)
	{
		LOGERROR("WFAudioRecorder::UnInit:waveInReset failed with return value = "<<mmr<<".");

		ret--;
	}

	mmr = waveInStop(_device);

	if (mmr != MMSYSERR_NOERROR)
	{
		LOGERROR("WFAudioRecorder::UnInit:waveInStop failed with return value = "<<mmr<<".");

		ret--;
	}

	mmr = waveInReset(_device);

	if (mmr != MMSYSERR_NOERROR)
	{
		LOGERROR("WFAudioRecorder::UnInit:waveInReset failed with return value = "<<mmr<<".");

		ret--;
	}

	mmr = waveInClose(_device);

	if (mmr != MMSYSERR_NOERROR)
	{
		LOGERROR("WFAudioRecorder::UnInit:waveInClose failed with return value = "<<mmr<<".");

		ret--;
	}

	FreeBlocks();

	return ret;
}
tByte* WFAudioRecorder::AllocateBlocks(tUInt32 count, tUInt32 size)
{
	tByte* buffer;

    tUInt32 totalBufferSize = (size + sizeof(WAVEHDR)) * count;

	if (count < 0 || size < 0)
	{
		return NULL;
	}

    /**
     * allocate memory for the entire set in one go
     */
	if (NULL == (buffer = (tByte*)malloc(totalBufferSize*sizeof(tByte))))
	{
		fprintf(stderr, "Memory allocation error\n");

		LOGERROR("WFAudioRecorder::AllocateBlocks:	HeapAlloc failed.");

        return NULL;
	}

	memset((void*)buffer, 0x00, totalBufferSize);

    /**
     * and set up the pointers to each bit
     */
    _blocks = (WAVEHDR*)buffer;

    buffer += sizeof(WAVEHDR) * count;

    for(tUInt32 i = 0; i < count; i++) 
	{
		_blocks[i].dwBufferLength = size;
		_blocks[i].lpData = (LPSTR)buffer;
		buffer += size;
	}

    return (tByte*)_blocks;
}
void WFAudioRecorder::FreeBlocks()
{
	/* 
    * and this is why allocateBlocks works the way it does
    */ 
    if (_blocks)
	{
		free(_blocks);
		_blocks = NULL;
	}
}

void WFAudioRecorder::Shutdown()
{
	_shutdown_requested = TRUE;
}
void WFAudioRecorder::SetCallBackThreadId(tUInt32 threadid)
{
	_callback_thread_id = threadid;
}